#include <xrpl/basics/contract.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/json/Object.h>
#include <xrpl/json/Output.h>
#include <xrpl/json/Writer.h>
#include <xrpl/json/json_value.h>

#include <stdexcept>
#include <utility>

namespace Json {

Collection::Collection(Collection* parent, Writer* writer)
    : parent_(parent), writer_(writer), enabled_(true)
{
    checkWritable("Collection::Collection()");
    if (parent_)
    {
        check(parent_->enabled_, "Parent not enabled in constructor");
        parent_->enabled_ = false;
    }
}

Collection::~Collection()
{
    if (writer_)
        writer_->finish();
    if (parent_)
        parent_->enabled_ = true;
}

Collection&
Collection::operator=(Collection&& that) noexcept
{
    parent_ = that.parent_;
    writer_ = that.writer_;
    enabled_ = that.enabled_;

    that.parent_ = nullptr;
    that.writer_ = nullptr;
    that.enabled_ = false;

    return *this;
}

Collection::Collection(Collection&& that) noexcept
{
    *this = std::move(that);
}

void
Collection::checkWritable(std::string const& label)
{
    if (!enabled_)
        ripple::Throw<std::logic_error>(label + ": not enabled");
    if (!writer_)
        ripple::Throw<std::logic_error>(label + ": not writable");
}

//------------------------------------------------------------------------------

Object::Root::Root(Writer& w) : Object(nullptr, &w)
{
    writer_->startRoot(Writer::object);
}

Object
Object::setObject(std::string const& key)
{
    checkWritable("Object::setObject");
    if (writer_)
        writer_->startSet(Writer::object, key);
    return Object(this, writer_);
}

Array
Object::setArray(std::string const& key)
{
    checkWritable("Object::setArray");
    if (writer_)
        writer_->startSet(Writer::array, key);
    return Array(this, writer_);
}

//------------------------------------------------------------------------------

Object
Array::appendObject()
{
    checkWritable("Array::appendObject");
    if (writer_)
        writer_->startAppend(Writer::object);
    return Object(this, writer_);
}

Array
Array::appendArray()
{
    checkWritable("Array::makeArray");
    if (writer_)
        writer_->startAppend(Writer::array);
    return Array(this, writer_);
}

//------------------------------------------------------------------------------

Object::Proxy::Proxy(Object& object, std::string const& key)
    : object_(object), key_(key)
{
}

Object::Proxy
Object::operator[](std::string const& key)
{
    return Proxy(*this, key);
}

Object::Proxy
Object::operator[](Json::StaticString const& key)
{
    return Proxy(*this, std::string(key));
}

//------------------------------------------------------------------------------

void
Array::append(Json::Value const& v)
{
    auto t = v.type();
    switch (t)
    {
        case Json::nullValue:
            return append(nullptr);
        case Json::intValue:
            return append(v.asInt());
        case Json::uintValue:
            return append(v.asUInt());
        case Json::realValue:
            return append(v.asDouble());
        case Json::stringValue:
            return append(v.asString());
        case Json::booleanValue:
            return append(v.asBool());

        case Json::objectValue: {
            auto object = appendObject();
            copyFrom(object, v);
            return;
        }

        case Json::arrayValue: {
            auto array = appendArray();
            for (auto& item : v)
                array.append(item);
            return;
        }
    }
    UNREACHABLE("Json::Array::append : invalid type");  // LCOV_EXCL_LINE
}

void
Object::set(std::string const& k, Json::Value const& v)
{
    auto t = v.type();
    switch (t)
    {
        case Json::nullValue:
            return set(k, nullptr);
        case Json::intValue:
            return set(k, v.asInt());
        case Json::uintValue:
            return set(k, v.asUInt());
        case Json::realValue:
            return set(k, v.asDouble());
        case Json::stringValue:
            return set(k, v.asString());
        case Json::booleanValue:
            return set(k, v.asBool());

        case Json::objectValue: {
            auto object = setObject(k);
            copyFrom(object, v);
            return;
        }

        case Json::arrayValue: {
            auto array = setArray(k);
            for (auto& item : v)
                array.append(item);
            return;
        }
    }
    UNREACHABLE("Json::Object::set : invalid type");  // LCOV_EXCL_LINE
}

//------------------------------------------------------------------------------

namespace {

template <class Object>
void
doCopyFrom(Object& to, Json::Value const& from)
{
    XRPL_ASSERT(from.isObjectOrNull(), "Json::doCopyFrom : valid input type");
    auto members = from.getMemberNames();
    for (auto& m : members)
        to[m] = from[m];
}

}  // namespace

void
copyFrom(Json::Value& to, Json::Value const& from)
{
    if (!to)  // Short circuit this very common case.
        to = from;
    else
        doCopyFrom(to, from);
}

void
copyFrom(Object& to, Json::Value const& from)
{
    doCopyFrom(to, from);
}

WriterObject
stringWriterObject(std::string& s)
{
    return WriterObject(stringOutput(s));
}

}  // namespace Json
